package com.dongrj.framework.core.utils;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Slf4j
public final class MoneyUtils {

    public static BigDecimal nullToZero(BigDecimal bigDecimal) {
        return nullToNum(bigDecimal, BigDecimal.ZERO);
    }

    public static boolean greaterThanZero(BigDecimal bigDecimal) {
        if (Objects.isNull(bigDecimal)) {
            return false;
        }
        return bigDecimal.compareTo(BigDecimal.ZERO) > 0;
    }

    public static BigDecimal zeroToNull(BigDecimal bigDecimal) {
        if (bigDecimal == null) {
            return null;
        }
        if (bigDecimal.compareTo(BigDecimal.ZERO) == 0) {
            return null;
        }
        return bigDecimal;
    }


    public static BigDecimal nullToNum(BigDecimal bigDecimal, BigDecimal num) {
        if (Objects.isNull(bigDecimal)) {
            return num;
        }
        return bigDecimal;
    }

    public static BigDecimal add(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return bigDecimal1.add(bigDecimal2);
    }

    public static BigDecimal subtract(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return bigDecimal1.subtract(bigDecimal2);
    }

    public static BigDecimal multiply(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return bigDecimal1.multiply(bigDecimal2);
    }

    public static BigDecimal multiplyDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return multiplyDown(bigDecimal1, bigDecimal2, 2);
    }

    public static BigDecimal multiplyDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2, int newScale) {
        return bigDecimal1.multiply(bigDecimal2).setScale(newScale, RoundingMode.DOWN);
    }


    public static BigDecimal divide(BigDecimal bigDecimal1, BigDecimal bigDecimal2, int newScale, RoundingMode roundingMode) {
        return bigDecimal1.divide(bigDecimal2, newScale, roundingMode);
    }

    public static BigDecimal divideDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2, int newScale) {
        return divide(bigDecimal1, bigDecimal2, newScale, RoundingMode.DOWN);
    }

    public static BigDecimal divideDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return divideDown(bigDecimal1, bigDecimal2, 2);
    }

    public static BigDecimal divideRateDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return divideDown(bigDecimal1, bigDecimal2, 8);
    }


    public static BigDecimal scale(BigDecimal bigDecimal) {
        return bigDecimal.setScale(2, RoundingMode.DOWN);
    }

    public static BigDecimal scale(BigDecimal bigDecimal, int newScale) {
        return bigDecimal.setScale(newScale, RoundingMode.DOWN);
    }

    public static BigDecimal scale(BigDecimal bigDecimal, int newScale, RoundingMode roundingMode) {
        return bigDecimal.setScale(newScale, roundingMode);
    }

    public static <T> BigDecimal sum(List<BigDecimal> list) {
        if (CollectionUtils.isEmpty(list)) {
            return BigDecimal.ZERO;
        }
        return list.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public static <T> BigDecimal sum(List<T> list, Function<T, BigDecimal> function) {
        return sum(list, function, BigDecimal.ZERO, BigDecimal::add);
    }

    public static <T> BigDecimal sum(List<T> list, Predicate<T> filter, Function<T, BigDecimal> function) {
        List<T> collect = list.stream().filter(filter).collect(Collectors.toList());
        return sum(collect, function, BigDecimal.ZERO, BigDecimal::add);
    }


    public static <T> BigDecimal sum(List<T> list, Function<T, BigDecimal> function, BigDecimal defaultR) {
        return sum(list, function, defaultR, BigDecimal::add);
    }

    public static <T, R> R sum(List<T> list, Function<T, R> function, R defaultR, BinaryOperator<R> accumulator) {
        if (CollectionUtils.isEmpty(list)) {
            return defaultR;
        }
        return list.stream().map(function).filter(Objects::nonNull).reduce(defaultR, accumulator);
    }

    public static <T> void shareMoney(BigDecimal shareMoney, List<T> ts, Function<T, BigDecimal> perMoneyFunction, BiConsumer<T, BigDecimal> setShareMoneyConsumer) {
        int size = CollectionUtils.size(ts);
        BigDecimal totalMoney = MoneyUtils.sum(ts, perMoneyFunction);
        log.info("shareMoney.size={},shareMoney={},totalMoney={}", size, shareMoney, totalMoney);
        if (size == 0) {
            return;
        }
        if (size == 1) {
            T t = ts.get(0);
            setShareMoneyConsumer.accept(t, shareMoney);
            return;
        }
        List<BigDecimal> allShareMoneyList = Lists.newArrayListWithCapacity(size);
        for (T t : ts) {
            BigDecimal perMoney = perMoneyFunction.apply(t);
            BigDecimal rate = MoneyUtils.divideDown(perMoney, totalMoney, 8);
            BigDecimal perShareMoney = MoneyUtils.multiplyDown(shareMoney, rate);
            log.info("shareMoney.perMoney={},rate={},perShareMoney={}", perMoney, rate, perShareMoney);
            if (perShareMoney.compareTo(perMoney) > 0) {
                log.error("shareMoney.moneyError.");
            }
            setShareMoneyConsumer.accept(t, perShareMoney);
            allShareMoneyList.add(perShareMoney);
        }
        int lastIndex = size - 1;
        List<BigDecimal> subShareMoneyList = allShareMoneyList.subList(0, lastIndex);
        BigDecimal subShareMoney = MoneyUtils.sum(subShareMoneyList);
        BigDecimal lastShareMoney = MoneyUtils.subtract(shareMoney, subShareMoney);
        T lastT = ts.get(lastIndex);
        BigDecimal perMoney = perMoneyFunction.apply(lastT);
        if (lastShareMoney.compareTo(perMoney) > 0) {
            log.error("lastShareMoney.moneyError.");
        }
        setShareMoneyConsumer.accept(lastT, lastShareMoney);
    }
}
